package math.region;

import math.math;
import math.vec2;
import math.vec3;

public class Ray3 implements Curve3
{
    
    public vec3 mOrig;
    
    public vec3 mDir;
    
    
    public Ray3()
    {
        this.mOrig = new vec3();
        this.mDir = new vec3();
    }
    
    public Ray3(
            vec3 aOrig, 
            vec3 aDir)
    {
        this();
        this.set(aOrig, aDir);
    }
    
    
    public final void set(
            vec3 aOrig, 
            vec3 aDir)
    {
        this.mOrig.set(aOrig);
        this.setDir(aDir);
    }
    
    public final void set(Ray3 aOther)
    {
        this.set(aOther.mOrig, aOther.mDir);
    }
    
    public final void setDir(vec3 aDir)
    {
        this.mDir.set(aDir);
        this.mDir.norm();
    }
    
    
    public boolean onCurve(vec3 aPoint)
    {
        vec3 localToPt = vec3.sub(aPoint, this.mOrig);
        localToPt.norm();
        double dot = localToPt.dot(this.mDir);
        boolean contains = math.abs(math.abs(dot) - 1.0) < math.EPSILON;
        return contains;
    }
    
    
    public vec3 point(double aT)
    {
        vec3 pt = vec3.mul(this.mDir, aT);
        pt.add(this.mOrig);
        return pt;
    }
    
    
    public double intersect(Sphere3 sphere)
    {
        vec3 subAC = vec3.sub(this.mOrig, sphere.mOrig);
        double alpha = this.mDir.dot(subAC);
        double beta = this.mDir.dot(this.mDir);
        double gamma = subAC.dot(subAC);
        double sqR = sphere.mRadius * sphere.mRadius;
        
        double delta = alpha * alpha - beta * (gamma - sqR);
        if (delta < 0.0)
        {
            return -1.0;
        }
        double rtDelta = math.sqrt(delta);
        double r0 = -(alpha + rtDelta) / beta;
        double r1 = -(alpha - rtDelta) / beta;
//        return math.min(r0, r1);
        
        final double EPSIL = 1.0e-3;
        boolean r0pos = r0 >= EPSIL;
        boolean r1pos = r1 >= EPSIL;
        if (r0pos && r1pos)
        {
            return math.min(r0, r1);
        }
        else if (r0pos)
        {
            return r0;
        }
        else if (r1pos)
        {
            return r1;
        }
        return -1.0;
    }
    
    public double intersect(Cylinder3 cyl)
    {
        final vec3 a = this.mOrig;
        final vec3 b = this.mDir;
        final vec3 c = cyl.mOrig0;
        final vec3 d = cyl.mOrig1;
        
        double sqR = cyl.mRadius * cyl.mRadius;
        
        vec3 alpha = vec3.sub(a, c);
        vec3 beta = vec3.sub(d, c);
        
        double sqB = b.dot(b);
        double sqAlpha = alpha.dot(alpha);
        double sqBeta = beta.dot(beta);
        
        double dotBAlpha = b.dot(alpha);
        double dotBBeta = b.dot(beta);
        double dotAlphaBeta = alpha.dot(beta);
        
        double A = sqB - dotBBeta * dotBBeta / sqBeta;
        double B = 2.0 * (dotBAlpha - dotBBeta * dotAlphaBeta / sqBeta);
        double C = sqAlpha - dotAlphaBeta * dotAlphaBeta / sqBeta - sqR;
        
        double delta = B * B - 4.0 * A * C;
        if (delta <= 0.0)
        {
            return -1.0;
        }
        double rtDelta = math.sqrt(delta);
        
        double kappa = -0.5 * B / A;
        double lambda = 0.5 * rtDelta / A;
        
        double r0 = kappa + lambda;
        double r1 = kappa - lambda;
        
        double tBoundLo = -1.0 * dotAlphaBeta / dotBBeta;
        double tBoundHi = (sqBeta - dotAlphaBeta) / dotBBeta;
        
        final double EPSIL = 1.0e-3;
        boolean r0pos = r0 >= EPSIL;
        boolean r1pos = r1 >= EPSIL;
        if (r0pos && r1pos)
        {
            double t = math.min(r0, r1);
            if (t >= tBoundLo && t <= tBoundHi)
            {
                return t;
            }
        }
        else if (r0pos)
        {
            double t = r0;
            if (t >= tBoundLo && t <= tBoundHi)
            {
                return t;
            }
        }
        else if (r1pos)
        {
            double t = r1;
            if (t >= tBoundLo && t <= tBoundHi)
            {
                return t;
            }
        }
        return -1.0;
    }
    
    public double intersect(Plane3 aPlane)
    {
        double tDenom = this.mDir.dot(aPlane.mNorm);
        if (math.abs(tDenom) < math.EPSILON)
        {
            return -1.0;
        }
        double tNumer = aPlane.mNorm.dot(vec3.sub(aPlane.mOrig, this.mOrig));
        double minT = tNumer / tDenom;
        if (minT < math.EPSILON)
        {
            return -1.0;
        }
        return minT;
    }
    
    public double intersect(Circle3 aCircle)
    {
        double tDenom = this.mDir.dot(aCircle.mNorm);
        if (math.abs(tDenom) < math.EPSILON)
        {
            return -1.0;
        }
        double tNumer = aCircle.mNorm.dot(vec3.sub(aCircle.mOrig, this.mOrig));
        double minT = tNumer / tDenom;
        if (minT < math.EPSILON)
        {
            return -1.0;
        }
        vec3 pt = this.point(minT);
        vec3 toPt = vec3.sub(pt, aCircle.mOrig);
        double distSq = toPt.lengthSq();
        double radSq = aCircle.mRadius * aCircle.mRadius;
        boolean inSphere = (distSq <= radSq);
        if (!inSphere)
        {
            return -1.0;
        }
        return minT;
    }
    
    public double intersect(Rect3 aRect)
    {
        vec3 surfNorm = aRect.crossAB();
        surfNorm.norm();
        double tDenom = this.mDir.dot(surfNorm);
        if (math.abs(tDenom) < math.EPSILON)
        {
            return -1.0;
        }
        double tNumer = surfNorm.dot(vec3.sub(aRect.mOrig, this.mOrig));
        double minT = tNumer / tDenom;
        if (minT < math.EPSILON)
        {
            return -1.0;
        }
        vec3 toPt = this.point(minT); // lies in the plane of the Rect3.
        toPt.sub(aRect.mOrig);
        double u = toPt.projFraction(aRect.mA);
        double v = toPt.projFraction(aRect.mB);
        if (u < 0.0 || u > 1.0 || 
            v < 0.0 || v > 1.0)
        {
            return -1.0;
        }
        return minT;
    }
    
    public double intersect(Tri3 aTri)
    {
        vec3 surfNorm = aTri.crossAB();
        surfNorm.norm();
        double tDenom = this.mDir.dot(surfNorm);
        if (math.abs(tDenom) < math.EPSILON)
        {
            return -1.0;
        }
        double tNumer = surfNorm.dot(vec3.sub(aTri.mOrig, this.mOrig));
        double minT = tNumer / tDenom;
        if (minT < math.EPSILON)
        {
            return -1.0;
        }
        vec3 toPt = this.point(minT); // lies in the plane of the Rect3.
        toPt.sub(aTri.mOrig);
        double u = toPt.projFraction(aTri.mA);
        double v = toPt.projFraction(aTri.mB);
        if (u < 0.0 || u > 1.0 || 
            v < 0.0 || v > 1.0 || 
            u + v > 1.0)
        {
            return -1.0;
        }
        return minT;
    }
    
    
    public Ray3 copy()
    {
        Ray3 copy = new Ray3();
        copy.set(this);
        return copy;
    }
    
    
    public String toString()
    {
        StringBuilder sb = new StringBuilder();
        sb.append("Ray3\n   Orig: ");
        sb.append(this.mOrig);
        sb.append("\n   Dir: ");
        sb.append(this.mDir);
        return sb.toString();
    }
    
}
